﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AssetManagementSystem.Model.Dao.MSAccess;
using AssetManagementSystem.Model.IDao;
//Currently Using MSAccess DAOs, to change to another database,
// Simply change this reference to the new DAO package implementing the same interfaces.
using System.Data.OleDb;
using System.Data;

namespace AssetManagementSystem.Model {

    public static class DAOManager {
        //A list of tables that are expected to be found in the database, used for validation.
        private static readonly string[] DB_TABLES = { "Student" };
        private const string DB_CONN_STR_START = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=";
        private const string DB_CONN_STR_END = ";Persist Security Info=False";

        //The database connection string to be passed to all DAOs
        static string connectionString;
        
        //DAO Objects, created as required then reused.
        private static IStudentDAO studentDao;
        private static IAssetDAO assetDao;
        private static IGroupDAO groupDao;
        private static ILoanDAO loanDao;
        private static IHoldDAO holdDao;

        private static ISysPropertyDAO sysPropertyDao;

        /// <summary>
        /// Returns a student DAO object for use in student CRUD operations.
        /// </summary>
        /// <returns>A student DAO object for use in student CRUD operations</returns>
        public static IStudentDAO GetStudentDAO() {
            if (IsDatabaseURISet()) {
                //If the student dao hasn't been created yet, create it
                if(studentDao == null){
                    studentDao = new StudentDAO(connectionString);
                }
                //return the student dao
                return studentDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }

        /// <summary>
        /// Returns an asset DAO object for use in asset CRUD operations.
        /// </summary>
        /// <returns>An asset DAO object for use in asset CRUD operations</returns>
        public static IAssetDAO GetAssetDAO() {
            if (IsDatabaseURISet()) {
                //If the asset dao hasn't been created yet, create it
                if (assetDao == null) {
                    assetDao = new AssetDAO(connectionString);
                }
                //return the asset dao
                return assetDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }

        /// <summary>
        /// Returns a group DAO object for use in group CRUD operations.
        /// </summary>
        /// <returns>A group DAO object for use in group CRUD operations</returns>
        public static IGroupDAO GetGroupDAO() {
            if (IsDatabaseURISet()) {
                //If the asset dao hasn't been created yet, create it
                if (groupDao == null) {
                    groupDao = new GroupDAO(connectionString);
                }
                //return the group dao
                return groupDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }

        /// <summary>
        /// Returns a loan DAO object for use in loan CRUD operations.
        /// </summary>
        /// <returns>A loan DAO object for use in loan CRUD operations.</returns>
        public static ILoanDAO GetLoanDAO() {
            if (IsDatabaseURISet()) {
                //If the loan dao hasn't been created yet, create it
                if (loanDao == null) {
                    loanDao = new LoanDAO(connectionString);
                }
                //return the loan dao
                return loanDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }

        /// <summary>
        /// Returns a hold DAO object for use in hold CRUD operations.
        /// </summary>
        /// <returns>A hold DAO object for use in hold CRUD operations.</returns>
        public static IHoldDAO GetHoldDAO() {
            if (IsDatabaseURISet()) {
                //If the hold dao hasn't been created yet, create it
                if (holdDao == null) {
                    holdDao = new HoldDAO(connectionString);
                }
                //return the hold dao
                return holdDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }

        /// <summary>
        /// Returns a system property DAO object for use in property CRUD operations.
        /// </summary>
        /// <returns>A system property DAO object for use in property CRUD operations.</returns>
        public static ISysPropertyDAO GetSysPropertyDAO() {
            if (IsDatabaseURISet()) {
                //If the sys property dao hasn't been created yet, create it
                if (sysPropertyDao == null) {
                    sysPropertyDao = new SysPropertyDAO(connectionString);
                }
                //return the hold dao
                return sysPropertyDao;
            } else {
                throw new Exception("A database has not been provided");
            }
        }


        //====================================================================
        //  UTILITY METHODS - USEFUL WHEN PERFORMING OTHER OPERATIONS
        //====================================================================

        /// <summary>
        /// Returns true if the database URI has been set and is valid
        /// </summary>
        /// <returns>True if the database URI has been set and is valid</returns>
        public static bool IsDatabaseURISet() {
            return (connectionString != "" || connectionString != null);
        }

        /// <summary>
        /// Sets the URI for the database to be used by all DAOs provided by this manager
        /// </summary>
        /// <param name="URI">Database URI (file path)</param>
        public static void SetDatabaseURI(string URI) {
            //create the database connection
            connectionString = DB_CONN_STR_START + URI + DB_CONN_STR_END;
            //ensure the connection works and contains the tables needed for operation
            bool isValid = false;
            try {
                isValid = IsValidDatabase();
            } catch (Exception except) {
                connectionString = "";
                throw new Exception("A connection to the database could not be established. Details: " + except.Message);
            }
            if (!isValid) {
                //one or more tables missing..error
                connectionString = "";
                throw new Exception("The database does not contain one or more of the expected data tables");
            }

        }

        /// <summary>
        /// Checks the database to make sure all expected tables are present
        /// </summary>
        /// <returns>True if all expected tables exists within the database</returns>
        private static bool IsValidDatabase() {
            //Get a unique list of tables in the user selected database.
            HashSet<string> tableMap = new HashSet<string>();
            foreach (DataRow row in getSchema().Rows) {
                tableMap.Add(row["TABLE_NAME"].ToString());
            }
            //Check that expected tables are in the list
            foreach (string table in DB_TABLES) {
                //if the expected table isn't found, return false (Invalid)
                if (!tableMap.Contains(table))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Returns a DataTable containing the schema of the currently selected database
        /// </summary>
        /// <returns>DataTable containing the schema of the currently selected database</returns>
        private static DataTable getSchema() {
            OleDbConnection testConnection = new OleDbConnection(connectionString);
            try {
                testConnection.Open();
                DataTable schema = testConnection.GetSchema("Tables");
                return schema;
            } catch (Exception e) {
                throw e;
            } finally {
                testConnection.Close();
            }
        }

        //====================================================================
        //  END - UTILITY METHODS
        //====================================================================


        

    }
}
